package com.lzwork.gpt.utility.batchjob;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;

import com.alibaba.fastjson.JSONObject;
import com.lzwork.gpt.utility.batchjob.inner.InnerJob;
import com.lzwork.utility.data.TypeEnum;
import com.lzwork.utility.file.FileOperation;
import com.lzwork.utility.file.FileUtility;
import com.lzwork.utility.logger.LOGGER;
import com.lzwork.utility.str.StrUtility;
import com.lzwork.web.data.BoolResultInfo;

import lombok.Getter;

/**
 * @author LiZheng
 * @date 2024年7月30日 10:35:55
 */

public abstract class SingleJob
{
	public final static String INFO_FILE_NAME = "info.json";
	public final static String DUMMY_MARK = ".dummy";
	public final static String DEF_JOB_OWNER = "admin";
	protected SimpleDateFormat timeFormat = new SimpleDateFormat(StrUtility.DEF_DATE_FORMAT_COMMON);

	public static BoolResultInfo deleteJobFiles(File folder)
	{
		if (!folder.isDirectory())
			return BoolResultInfo.makeFalse("Target is not a folder.");

		List<String> fails = new ArrayList<>();

		File dummy = null;
		for (File f : folder.listFiles())
		{
			if (DUMMY_MARK.equals(f.getName()))
			{
				dummy = f;
				LOGGER.log(LOGGER.FILE, "Found dummy mark in folder: " + folder.getName());
			} else
			{
				boolean done = FileOperation.deleteFile(f);

				if (!done)
				{
					fails.add("Delete file fail: " + f.getName());
				}
			}
		}

		if (fails.isEmpty())
		{
			// delete the dummy mark after all other delete job done.
			if (dummy != null)
				FileOperation.deleteFile(dummy);
			FileOperation.deleteFile(folder);
			return BoolResultInfo.makeTrue("");
		}

		return BoolResultInfo.makeFalse(fails);
	}

	/**
	 * @param target
	 *            Another BaseEntity
	 * @param compareMethod
	 *            0-按名称 1-按状态 2-添加日期 3-场景类型
	 * @return Compare result by method
	 */
	public int compareTo(SingleJob target, int compareMethod)
	{
		switch (compareMethod)
		{
		case 0:
			return this.getJobId().compareTo(target.getJobId());
		case 1:
			return Integer.compare(this.getState().bit, target.getState().bit);
		case 2:
			return Long.compare(this.getBeginTick(), target.getBeginTick());
		case 3:
			return Integer.compare(this.getType().getId(), target.getType().getId());
		default:
			return this.getJobId().compareTo(target.getJobId());
		}
	}

	// info fields
	@Getter
	protected String jobId = "";
	@Getter
	protected String jobTitle = "";

	@Getter
	protected String user = "";

	@Getter
	private JobState state = JobState.Unknown;

	public void setState(JobState s)
	{
		state = s;
	}

	@Getter
	protected long beginTick;

	@Getter
	protected File jobFolder;

	@Getter
	protected File source;

	protected File output;

	public abstract File defaultOutputFile();

	public File getOutputFile()
	{
		return output;
	}

	protected String breakMsg = "";

	protected List<String> logs = new ArrayList<>();

	// runtime fields
	protected SimpleDateFormat dateFormat = new SimpleDateFormat(StrUtility.DEF_DATE_FORMAT_COMMON);

	protected Thread jobThread = null;

	protected abstract InnerJob innerJob();

	public abstract TypeEnum getType();

	protected void doInnerJob() throws InterruptedException
	{
		innerJob().doJob();
	}

	public void setOutput(File output)
	{
		if (!output.exists())
			return;

		if (this.output.equals(output))
			return;

		this.output = output;
		saveInfo();
	}

	// protected CheckJob

	public BoolResultInfo doJob()
	{
		if (jobThread != null)
		{
			boolean live = jobThread.isAlive();
			if (live)
				return BoolResultInfo.makeFalse("Job is running.");
		}

		clear();

		jobThread = new Thread("Job-" + getJobId())
		{
			public void run()
			{
				try
				{
					doInnerJob();
				} catch (InterruptedException e)
				{
					String info = e.getMessage() + " State: [" + state.getName() + "]";
					onJobBreak(e.getMessage(), true);
					LOGGER.log(LOGGER.APP, "Job thread break: " + info);
				} catch (Exception e)
				{
					onJobBreak(LOGGER.exceptionInfo(e));
					e.printStackTrace();
				}
			}
		};
		state = JobState.Work;
		jobThread.start();

		return BoolResultInfo.makeTrue("");
	}

	protected void clear()
	{
		if (output != null && output.exists())
			FileOperation.deleteFile(output);
		breakMsg = "";
		logs.clear();
		saveInfo();
	}

	public BoolResultInfo reset()
	{
		boolean isRunning = stopJob(false);

		if (!isRunning)
		{
			state = JobState.Idel;
			clear();
		}

		return BoolResultInfo.makeTrue("");
	}

	public boolean stopJob(boolean force)
	{
		if (isRunning())
		{
			if (force)
				addLog("Force stop job.");
			else
				addLog("Stop job manually.");

			innerJob().stop(force);
			jobThread.interrupt();

			state = force ? JobState.Break : JobState.Stop;

			return true;
		}

		return false;
	}

	public void onJobBreak(String msg)
	{
		onJobBreak(msg, false);
	}

	public abstract void onJobBreak(String msg, boolean keepState);

	public boolean isRunning()
	{
		if (innerJob() == null)
			return false;

		if (jobThread == null)
			return false;

		if (jobThread.isInterrupted())
			return false;

		if (!jobThread.isAlive())
			return false;

		return true;
	}

	public void onJobDone()
	{
		state = JobState.Done;
		jobThread = null;
		saveInfo();
	}

	protected JobOption option;

	public JobOption getOption()
	{
		return option;
	}

	public BoolResultInfo setOptionData(Map<?, ?> map)
	{
		BoolResultInfo result = option.setData(map);

		if (result.isDone())
			saveInfo();

		return result;
	}

	protected abstract void saveInfo();

	public void addLog(String msg)
	{
		synchronized (logs)
		{
			String time = "[" + timeFormat.format(new Date(System.currentTimeMillis())) + "]";
			logs.add(time + "\t" + msg);
		}
	}

	public String getLog()
	{
		String str = "";
		synchronized (logs)
		{
			str = StrUtility.combineString(logs, "\n");
		}
		return str;
	}

	protected String getProgress()
	{
		InnerJob job = innerJob();
		return job == null ? "0/0" : job.progress();
	}

	public JSONObject info()
	{
		if (state == JobState.Stop && !isRunning())
			state = JobState.Idel;

		JSONObject obj = new JSONObject(true);

		obj.put("id", jobId);
		obj.put("title", jobTitle);
		obj.put("source", source.getName());
		obj.put("username", user);
		obj.put("time", dateFormat.format(new Date(beginTick)));
		obj.put("type", getType().getName());

		obj.put("state", state.getId());
		String stateStr = state.getName();

		if (state == JobState.Break)
			stateStr += breakMsg;
		else if (state == JobState.Work)
			stateStr += ": " + getProgress();
		obj.put("stateStr", stateStr);

		return obj;
	}

	public File dummyMark()
	{
		return FileUtility.getFileSafe(jobFolder, DUMMY_MARK);
	}

	public boolean isValid()
	{
		return state != JobState.Unknown;
	}
}
